package cn.zwx.learn.basic.lock;

import cn.hutool.core.collection.CollectionUtil;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

/**
 * @description第一个问题:
 *  我们完全可以用一个变量Thread owner用来存被占用的线程，
 *  如果该变量owner有值，说明锁被占用。不过这里有个小坑，这个变量需要时volatile修饰的，
 *  防止多线程之间无法获取最新的状态。
 * @description第二个问题:
 *  我们可以直接采用CAS机制来保证操作的原子性问题。
 *  CAS（Compare and swap），即比较并交换，也是实现我们平时所说的自旋锁或乐观锁的核心操作。
 *  它的实现很简单，就是用一个预期的值和内存值进行比较，如果两个值相等，就用预期的值替换内存值，
 *  并返回 true。否则，返回 false。
 * @description第三个问题:
 * 线程的通信方式有：
 * suspend和resume:
 * JDK 已废弃
 * 不释放锁
 * 要求执行顺序
 * wait/notify/notifyAll：
 * 要求执行顺序
 * 释放锁
 * park/unpark :
 * 不释放锁
 * 不要求执行顺序
 * suspend和resume 已经被废弃了，所以不考虑。
 * wait/notify 需要用到synchronize，既然要用到synchronize标识了，我们还实现个锤子的锁，所以也不考虑。
 * park/unpark 不释放锁，没问题，我们本来就没用到Synchronize，无所谓。不要求park/unpark执行顺序，那更好了，
 * 这样还可以防止死锁问题。所以我们就选它了！
 * @description第四个问题:
 * 排队阻塞的线程就暂时用集合将其存起来，等释放锁后，再将它们用unpark唤醒
 **/
/**
 * @description: 自定义实现Lock
 * @projectName:java-basic
 * @see:cn.zwx.learn.basic.lock
 * @author:zhangwenxue
 * @createTime:2020/12/10 12:17
 * @version:1.0
 */
public class MyLock {
    // 变量owner存储占用锁的线程
    private volatile AtomicReference<Thread> owner = new AtomicReference<>();
    // 阻塞队列
    private volatile LinkedBlockingQueue<Thread> waits = new LinkedBlockingQueue<>();

    /**
     * @description 获取锁的方法
     * @return void
     * @author zhangwenxue
     * @createTime 2020/12/10 12:21
     **/
    public void lock(){
        // 尝试获取锁的方法 这里模拟自旋去不但重试
//        其中一个原因是，我们需要让线程重新去争夺锁资源。
//        第二个原因是，线程的伪唤醒（spurious wakeup），对于LockSupport.park()来说就是除了unpark和interrupt之外的原因。
        while (!tryLock()){
            // 没有获取到锁,则放在阻塞队列
            waits.add(Thread.currentThread());
            // 阻塞当前线程
            LockSupport.park();
        }
        // 如果拿到锁则移除等待队列
        waits.remove(Thread.currentThread());
    }

    /**
     * @description 尝试获取锁的方法
     * @return boolean
     * @author zhangwenxue
     * @createTime 2020/12/10 12:23
     **/
    private boolean tryLock() {
        // 用cas去操作 如果当前owner为null,则把当前线程设置为owner
       return owner.compareAndSet(null,Thread.currentThread());
    }

    /**
     * @description 释放锁
     * @return void
     * @author zhangwenxue
     * @createTime 2020/12/10 12:37
     **/
    public void unLock(){
        // 使用cas机制 去判断当前线程是否是拿到锁的线程
        if (owner.compareAndSet(Thread.currentThread(),null)){
            // 如果成功则唤醒阻塞队列的所有线程
            if (CollectionUtil.isNotEmpty(waits)){
                waits.stream().forEach(w->{
                    // 唤醒线程
                    LockSupport.unpark(w);
                });
            }
        }
    }
}
